package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.FreemarkerService;
import com.heima.article.service.HotArticleComputeService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.BehaviorConstants;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.ArticleInfoBehaviorVO;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {


    @Autowired
    ApArticleMapper apArticleMapper;
    @Autowired
    ApArticleContentMapper apArticleContentMapper;
    @Autowired
    ApArticleConfigMapper apArticleConfigMapper;

    @Autowired
    FreemarkerService freemarkerService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     *
     * @param type    1加载更多    2加载最新
     * @param dto
     * @return
     */
    @Override
    public ResponseResult load(Short type, ArticleHomeDto dto) {
        //1.校验参数
        if(dto==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        if(StringUtils.isBlank(dto.getTag())){
            dto.setTag(ArticleConstants.DEFAULT_TAG);//代表查询所有频道
        }
        if(dto.getSize()==null || dto.getSize()<0 || dto.getSize()>50){
            dto.setSize(10);//默认查询10条
        }
        if(dto.getMaxBehotTime()==null){
            dto.setMaxBehotTime(new Date());
        }
        if(dto.getMinBehotTime()==null){
            dto.setMinBehotTime(new Date());
        }

        //2.查询db  mybatis多表查询
        List<ApArticle> list =  apArticleMapper.loadArticle(type, dto);


        //3.返回
        return ResponseResult.okResult(list);
    }

    @Override
    public ResponseResult saveArticle(ArticleDto dto) {

        log.info("ApArticleServiceImpl  saveArticle 处理详情数据   线程名称:{}", Thread.currentThread().getName());

        //1.校验
        if(dto==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //其他参数校验...


        ApArticle article = new ApArticle();
        BeanUtils.copyProperties(dto, article);
        //2.判断是否存在文章id
        if(dto.getId()==null){
            //2.1不存在则新增
            this.save(article);//新增 ap_article

            ApArticleConfig config = new ApArticleConfig(article.getId());
            apArticleConfigMapper.insert(config);//新增ap_article_config

            ApArticleContent content = new ApArticleContent();
            content.setArticleId(article.getId());
            content.setContent(dto.getContent());
            apArticleContentMapper.insert(content);//新增ap_article_content

        }else{
            //2.2存在id则修改
            this.updateById(article);

            //根据文章id查询content
            ApArticleContent apArticleContentDB = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
            apArticleContentDB.setContent(dto.getContent());
            apArticleContentMapper.updateById(apArticleContentDB);
        }

        //处理文章详情页   静态化后得到源代码  存储到minio 同时更新ap_article表的static_url字段     异步执行
        freemarkerService.buildArticleContentToMinio(article, dto.getContent());


        //3.返回
        return ResponseResult.okResult(article.getId().toString());
        //转string后再返回，如果返回long类型精度可能会丢失
        //将文章id ap_article表主键  转字符串返回，  自媒体微服务修改wm_news表状态为9（已发布）同时保存 article_id
    }


    @Override
    public ResponseResult loadArticleBehaviro(ArticleInfoDto dto) {
        //1.校验
        if(dto==null ||dto.getArticleId()==null || dto.getAuthorId()==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //校验是否登陆
        WmUser user = WmThreadLocalUtil.getUser();
        if(user==null){
            //说明没有登陆
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }
        if(user.getId()==0){
            //说明用户点的不登录先看看  不能返回needlogin    按正常的结果返回
            ArticleInfoBehaviorVO vo = new ArticleInfoBehaviorVO();
            return ResponseResult.okResult(vo);
        }


        //2.查询redis 判断当前登陆人是否对文章做过点赞   是否对作者做过关注

        ArticleInfoBehaviorVO ret = new ArticleInfoBehaviorVO();

        String articleId = dto.getArticleId().toString();// 文章id
        String authorId = dto.getAuthorId().toString();// 作者id
        String userId = user.getId().toString();// 当前登录人id

        //判断该文章的点赞集合中有没有当前登录人
        BoundSetOperations<String, String> articleLikesOps = stringRedisTemplate.boundSetOps(BehaviorConstants.LIKE_BEHAVIOR + articleId);
        Boolean member = articleLikesOps.isMember(userId);
        if(member){
            //说明当前登录人人 对文章 点过赞
            ret.setIslike(true);
        }

        //判断当前登录人  是否对 作者  点过关注
        BoundSetOperations<String, String> followOps = stringRedisTemplate.boundSetOps(BehaviorConstants.APUSER_FOLLOW_RELATION + userId);
        Boolean member1 = followOps.isMember(authorId);
        if(member1){
            //说明当前登录人 对 作者关注过
            ret.setIsfollow(true);
        }


        //3.将结果封装对象返回
        return ResponseResult.okResult(ret);
    }










    /**
     * 根据参数加载文章列表  v2
     * @param loadType  1 加载更多   2 加载最新
     * @param dto
     * @param firstPage  是否是首页
     * @return
     */
    @Override
    public ResponseResult load2(Short loadType, ArticleHomeDto dto, boolean firstPage) {
        if (firstPage) {
            String jsonStr = stringRedisTemplate.boundValueOps(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag()).get();

            if (StringUtils.isNotBlank(jsonStr)) {
                List<HotArticleVo> hotArticleVoList = JSON.parseArray(jsonStr, HotArticleVo.class);
                ResponseResult responseResult = ResponseResult.okResult(hotArticleVoList);
                return responseResult;
            }
        }
        return load(loadType,dto);
    }


    @Autowired
    HotArticleComputeService hotArticleComputeService;

    /**
     * 得到10秒内聚合结果  更新文章分值    db  redis
     * @param mess
     */
    @Override
    public void updateArticleScore(ArticleVisitStreamMess mess) {
        //1.更新 ap_article表中  行为数据
        ApArticle article = updateApArticle(mess);


        //2.重新计算文章分值  将分值*3   当前的行为操作 权重更高
        Integer score = computeScore(article);
        score = score * 3;


        //3.替换当前文章对应频道的热点数据
        //hot_article_first_page_文章的频道id
        replaceDataToRedis(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + article.getChannelId());

        //4.替换推荐对应的热点数据
        //hot_article_first_page___all__
        replaceDataToRedis(article, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);

    }

    /**
     * 替换redis中对应的文章数据
     * @param article
     * @param score
     * @param key
     */
    private void replaceDataToRedis(ApArticle article, Integer score, String key) {
        //从redis中获取 文章列表
        String articleJSONStr = stringRedisTemplate.boundValueOps(key).get();
        if(org.apache.commons.lang3.StringUtils.isNotBlank(articleJSONStr)){
            List<HotArticleVo> hotArticleVoList = JSON.parseArray(articleJSONStr, HotArticleVo.class);

            boolean flag=false; //代表集合中是否有 当前文章
            //判断article  文章 是否在文章列表中  在则修改分值    不在则添加
            for (HotArticleVo vo : hotArticleVoList) {
                if(vo.getId().equals(article.getId())){
                    //说明文章在 集合中
                    vo.setScore(score);
                    flag =true;
                    break;
                }
            }
            if(!flag){
                //说明集合中没有该文章
                HotArticleVo tmp = new HotArticleVo();
                BeanUtils.copyProperties(article, tmp);
                tmp.setScore(score);
                hotArticleVoList.add(tmp);
            }

            //文章列表按分值倒序 取前30条 设置到redis
            List<HotArticleVo> list = hotArticleVoList
                    .stream()
                    .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())//将集合中文章对象  按分数倒序
                    .limit(30)//取前30条
                    .collect(Collectors.toList());

            stringRedisTemplate.boundValueOps(key).set(JSON.toJSONString(list));
        }
    }


    /**
     * 计算文章分值
     * 阅读权重是1   点赞是3  评论是5   收藏是8
     * @param vo
     * @return
     */
    private Integer computeScore(ApArticle vo) {
        Integer score = 0;
        if(vo.getViews()!=null){
            score += vo.getViews().intValue() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if(vo.getLikes()!=null){
            score += vo.getLikes().intValue() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if(vo.getComment()!=null){
            score += vo.getComment().intValue() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if(vo.getCollection()!=null){
            score += vo.getCollection().intValue() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }

    /**
     * 更新文章行为数据   更新的频率 取决于  聚合的时间窗口大小 和用户行为
     * @param mess
     * @return
     */
    private ApArticle updateApArticle(ArticleVisitStreamMess mess) {
        ApArticle apArticle = getById(mess.getArticleId());
        apArticle.setCollection( ( apArticle.getCollection()==null ? 0: apArticle.getCollection() ) + mess.getCollect());
        apArticle.setComment((apArticle.getComment()==null ? 0 : apArticle.getComment())+mess.getComment());
        apArticle.setLikes((apArticle.getLikes()==null? 0 : apArticle.getLikes())+mess.getLike());
        apArticle.setViews((apArticle.getViews()==null? 0 : apArticle.getViews())+mess.getView());
        updateById(apArticle);
        return apArticle;
    }
}
